package com.xing.binbackend.topology.remote;

import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.xing.binbackend.topology.model.CloudService;
import com.xing.binbackend.topology.model.CmdbRelation;
import com.xing.binbackend.util.ReadFileUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class CmdbCacheService {
    private final CmdbClientService cmdbClientService;
    private final ReadFileUtils readFileUtils;
    private final LoadingCache<String, Map<String, CloudService>> cloudServiceCache;
    @Resource
    private LoadingCache<String, Map<String, Object>> loadingCache;
    //  expireAfterWrite：
    //      该方法设置的是缓存项在写入之后经过指定的时间就会过期。
    //      当缓存项过期后，再次访问该缓存项时，会同步地触发 CacheLoader 的 load
    //      方法重新加载数据，在数据加载完成之前，访问线程会被阻塞，直到新数据加载完成并返回。
    //  refreshAfterWrite
    //      它设置的是缓存项在写入之后经过指定的时间，下次访问该缓存项时，会在后台异步地重新加载数据，
    //      同时立即返回旧值（如果存在）。这意味着访问线程不会被阻塞，用户可以快速得到旧数据，
    //      而新数据会在后台慢慢加载更新缓存。
    public CmdbCacheService(CmdbClientService cmdbClientService, ReadFileUtils readFileUtils) {
        this.cmdbClientService = cmdbClientService;
        this.readFileUtils = readFileUtils;
        this.cloudServiceCache =
                CacheBuilder.newBuilder().refreshAfterWrite(1, TimeUnit.MINUTES).build(new CacheLoader<String, Map<String, CloudService>>() {
                    // 如果键对应值不在缓存中，LoadingCache会自动调用load方法加载数据，放入缓存
                    @Override
                    public Map<String, CloudService> load(String ip) {
                        log.info("refresh cloud service, ip: {}", ip);
                        return loadingCloudService(ip);
                    }
                });
    }

    public void processCmdbRelations(String ip, String jsonFilePath) throws IOException {
        List<CmdbRelation> cmdbRelations = readFileUtils.readCmdbRelations();
        // 遍历第一个
        for (CmdbRelation relation : cmdbRelations) {
            List<JsonNode> dataList = queryDataFromCmdb(ip, relation);
            // 如果 reach true 就要返回key
            if (!relation.isReachTarget()) {
                updateLoadingCache(ip, relation.getQueryCIName(), dataList);
            } else {
            }
            }
        }

        private List<JsonNode> queryDataFromCmdb (String ip, CmdbRelation relation){
            switch (relation.getQueryCIType()) {
                case "INSTANCE":
                    return cmdbClientService.queryInstances(ip, relation.getQueryCIName(),
                            relation.getQuerySelects(), "");
                case "RELATION":
                    return cmdbClientService.queryRelations(ip, relation.getQueryCIName(),
                            relation.getQuerySelects(), "");
                default:
                    return Collections.emptyList();
            }
        }

        private void updateLoadingCache (String ip, String queryCIName, List < JsonNode > dataList){
            try {
                Map<String, Object> ipCache = loadingCache.get(ip);
                if (ipCache == null) {
                    ipCache = new HashMap<>();
                }
                ipCache.put(queryCIName, dataList);
                loadingCache.put(ip, ipCache);
            } catch (Exception e) {
                throw new RuntimeException("Failed to update loading cache", e);
            }
        }

        private Map<String, CloudService> loadingCloudService (String ip){
            log.info("loadingCloudService, ip: {}", ip);
            Map<String, CloudService> cloudServiceMap = new HashMap<>();
            List<JsonNode> msNodes = cmdbClientService.queryInstances(ip, "cloudService",
                    Arrays.asList("id", "name"), "");
            for (JsonNode msNode : msNodes) {
                String id = msNode.path("id").asText();
                cloudServiceMap.put(id,
                        CloudService.builder().id(id).name(msNode.path("name").asText()).build());
            }
            return cloudServiceMap;
        }

        public Map<String, CloudService> getCloudService (String ip){
            try {
                return cloudServiceCache.get(ip);
            } catch (ExecutionException e) {
                throw new RuntimeException(e);
            }
        }
    }